feat(factors): reproduce valley/ridge VWAP-ratio factor (PR-J, both axes PASS but 4.4x turnover kills tradability) - #70
Merged
Conversation
The peak/ridge/valley classification already computed `eruptive` internally but exposed only `classifiable` / `valley` / `peak`. Add `ridge` = eruptive & ~peak so the ridge-price family (PR-J) consumes THE SAME classification instead of re-deriving it and drifting apart from PR-F / PR-H / PR-I. Purely additive: the three pre-existing masks are computed exactly as before and no consumer reads columns positionally. The three masks now partition the classifiable bars exactly (valley | peak | ridge == classifiable, pairwise disjoint). The rule is PINNED as the exact complement of the conservative peak test, so it is wider than the literal 'eruptive next to an eruptive': it also covers session-boundary eruptions and eruptions whose neighbour is unclassifiable, i.e. every eruptive bar whose isolation is not provable. Verified bit-identical on real cached minute data: volume_peak_count_20, peak_interval_kurtosis_20 and valley_relative_vwap_20 over 30 CSI500 names x 2022-2024 (21142 symbol-days) give assert_frame_equal(check_exact=True) and the same SHA256 b3e68795... before and after.
Reproduces the FOURTH factor of the Kaiyuan microstructure series #27 (谷岭加权价格比, reportId 4957417 §7.1) as ValleyRidgeVwapRatioFactor and runs it through the frozen StandardFactorEvaluator on real cached CSI500 data. Direct robustness test of PR-I: same reused classification, same Sigma-amount/Sigma-volume VWAP identity, same eval cell — only the DENOMINATOR changes, from the whole visible day's VWAP to the RIDGE VWAP, so the two behavioural groups collide head-on. Definition (PINNED, disclosed on the spec): daily ratio = valley VWAP / ridge VWAP, averaged over the trailing 20 VALID days. A day is valid iff it has >=100 classifiable bars, >=20 tradable valley bars, >=10 tradable ridge bars and positive volume in both denominators. The ridge floor is deliberately LOWER because a ridge bar must erupt AND fail the isolation test; the runner MEASURES and logs the realized ridge-bar distribution, the day-validity rate and the counterfactual valid-day count at a 20-bar floor, so the scarcity is a reported number rather than a hidden threshold. Prototype sanity (tmp/design/pr_j_prototype_sanity.txt): mean daily cross-sectional RankIC +0.0438 over 692 days on 30 CSI500 names, 2022-2024 — POSITIVE, matching the pre-registered sign +1. Full run (995/996 symbols, stk_mins_live_calls=0, 438s): both axes PASS as in PR-I — no-book Watch (predictive PASS, IC 0.0339, ICIR 0.425, CI low +0.355, NW-t 14.11, N_eff 854, win 66.8%, monotonicity 0.90); with-book Watch (incremental PASS, incremental ICIR 0.289). Day validity 51.5% of classifiable symbol-days, materially below PR-I's ~99.8% — disclosed, not hidden. Net long-short spread is NEGATIVE at every cost scenario (gross +0.0004/period vs 2.10 turnover x 0.001 fee); reported as-is, the frozen evaluator's known aligned-spread cost-sign issue is untouched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reproduce the valley/ridge weighted-price-ratio factor — the fourth factor of Kaiyuan Securities microstructure #27 (loop reproduction 7/10). It swaps PR-I's denominator (all-visible VWAP) for the ridge VWAP, contrasting the two behavioural groups directly.
Result: both assessed axes PASS (Watch/Watch) — IC +0.0339, ICIR 0.425 (CI low 0.355 > the 0.30 bar), NW-t 14.11, win rate 0.668, monotonicity 0.90, N_eff 854; incremental ICIR CI low +0.227 > 0.15. Prototype (persisted at tmp/design/pr_j_prototype_sanity.txt, run before the full run, same seed/names as PR-I for comparability): mean RankIC +0.0438, PASS.
What this establishes: the price-level signal survives the denominator swap, so it is a property of the family rather than an artifact of PR-I's specific denominator. Combined with the earlier two: from this one report, count-based (peak-minute count) is weakly positive, timing-based (peak-interval kurtosis) is null, and both price-level statistics pass.
But tradability collapses, and the mechanism is measured. Head-to-head against PR-I on the identical cell: gross long-short spread is essentially the same (+0.000396 vs +0.000387), yet turnover is 4.4x higher (2.101 vs 0.473) and the net spread is ~20x worse (−0.001705 vs −0.000085 at base cost; net is negative at 1x/2x/4x). Root cause is ridge scarcity: ridge bars/day median 10 (vs valley 205), so 48.5% of classifiable symbol-days fail the ≥10-ridge gate (557,428 of 1,149,313; 591,524 valid). That halves the cross-section (475 vs 929 names/day) and halves signal persistence (rank-autocorr half-life 8.0 vs 15.2 periods), so names churn in and out of the ranked universe. The IC survives; the tradability does not.
Note on the negative spread: unlike the sign=−1 factors, this is NOT affected by the frozen evaluator's aligned-spread cost-sign quirk — with sign=+1, aligned = net, so the reported figures are simply correct and bad. Independent sanity check: turnover 2.101 x fee 0.001 = 0.0021/period against a 0.0004/period gross spread.
Factor
valley_ridge_vwap_ratio_20: reuses the merged peak taxonomy (ridge = eruptive & ~peak; an isolated peak is in NEITHER leg); per valid day, ratio = valley VWAP / ridge VWAP via the Σamount/Σvolume identity per leg (non-positive volume/amount bars dropped before summation); factor = mean daily ratio over the trailing 20 valid days. Day valid: ≥100 classifiable AND ≥20 tradable valley AND ≥10 tradable ridge AND positive denominators; NaN below 10 valid days. The asymmetric ridge floor is pinned and disclosed (spec + config + report description) with its rationale — at a 20-bar floor only 335,920 days would survive instead of 591,524. Pre-registered sign +1.Honest caveats kept in the record: monotonicity is 0.90 not 1.0 (Q4 final NAV 1.667 slightly exceeds Q5's 1.607, so the top bucket is not the best performer); the median day sits exactly on the ridge threshold; 1 evaluation period has an empty cross-section and 1 symbol was dropped for missing minute coverage (both disclosed by the evaluator).
Test plan
git showand compared all THREE prior factors (volume_peak_count_20,peak_interval_kurtosis_20,valley_relative_vwap_20) on 5 real symbols x ~58k rows each — bit-identical; the newridgecolumn verified disjoint from valley and peak on real data.cross_section_size_mean(475.2 of a ~995 universe) corroborates the same magnitude. Folding factor-specific coverage into the report would need a change to the frozen layer.